﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using Red.Scripts.Defines;

namespace Language.Syntax.Colouring
{
    #region Tag

    internal class HighlightWordTag : TextMarkerTag
    {
        #region C-Tor

        public HighlightWordTag() : base( Classification.Name.DynamicHighlightWord )
        {
        }

        #endregion C-Tor
    }

    #endregion Tag

    #region Tagger

    internal class SelectionHighlighter : ITagger<HighlightWordTag>
    {
        #region Public Properties

        #region Services

        public ITextView TextView { get; private set; }
        public ITextStructureNavigator TextStructureNavigator { get; private set; }
        public ITextSearchService TextSearchService { get; private set; }
        public Settings Settings { get; private set; }

        #endregion Services

        #region Members

        public SnapshotSpan CurrentHighlight => TextView.Selection.StreamSelectionSpan.SnapshotSpan;
        public SnapshotSpan PreviousHighlight { get; private set; }
        public bool HighlightChanged => CurrentHighlight != PreviousHighlight;

        #endregion Members

        #endregion Public Properties

        #region C-Tor

        public SelectionHighlighter( ITextView textView, ITextStructureNavigatorSelectorService textStructureNavigatorSelector, ITextSearchService textSearchService, Settings settings )
        {
            TextView = textView;
            TextStructureNavigator = textStructureNavigatorSelector.GetTextStructureNavigator( textView.TextBuffer );
            TextSearchService = textSearchService;
            Settings = settings;

            textView.Selection.SelectionChanged += Selection_SelectionChanged;
        }

        #endregion C-Tor

        #region Private Methods

        private void Update()
        {
            if ( !Settings[ Setting.HighlightMatchingWordBehaviour ].Equals( HighlightMatchingWordBehaviour.Global ) )
                return;

            if ( HighlightChanged )
            {
                if( HasValidHighlight() )
                {
                    PreviousHighlight = CurrentHighlight;
                }
                else
                {
                    PreviousHighlight = new SnapshotSpan();
                }

                TagsChanged?.Invoke( this, new SnapshotSpanEventArgs( new SnapshotSpan( TextView.TextBuffer.CurrentSnapshot, 0, TextView.TextBuffer.CurrentSnapshot.Length ) ) );
            }
        }

        private bool HasValidHighlight()
        {
            if ( !TextView.Selection.IsEmpty && TextView.Selection.Mode == TextSelectionMode.Stream )
            {
                TextExtent extents = TextStructureNavigator.GetExtentOfWord( CurrentHighlight.Start );

                return extents.IsSignificant && CurrentHighlight == extents.Span;
            }

            return false;
        }

        private void Selection_SelectionChanged( object sender, EventArgs e )
        {
            Update();
        }

        #endregion Private Methods

        #region ITagger<HighlightWordTag>

        public event EventHandler<SnapshotSpanEventArgs> TagsChanged;

        public IEnumerable<ITagSpan<HighlightWordTag>> GetTags( NormalizedSnapshotSpanCollection spans )
        {
            // Only if there is a highlight to display
            if ( !PreviousHighlight.IsEmpty )
            {
                foreach ( var span in spans )
                {
                    FindData findData = new FindData( PreviousHighlight.GetText(), span.Snapshot );
                    findData.FindOptions = FindOptions.WholeWord | FindOptions.MatchCase;

                    int position = span.Start.Position;
                    SnapshotSpan? result;
                    while ( ( result = TextSearchService.FindNext( position, false, findData ) ) != null )
                    {
                        position = result.Value.End.Position;

                        // Make sure we don't highlight the user-selected text
                        if( result != PreviousHighlight )
                        {
                            yield return new TagSpan<HighlightWordTag>( result.Value, new HighlightWordTag() );
                        }
                    }
                }
            }
        }

        #endregion ITagger<HighlightWordTag>
    }

    #endregion Tagger

    #region Provider

    [Export( typeof( IViewTaggerProvider ) )]
    [ContentType( "redscript" )]
    [TagType( typeof( TextMarkerTag ) )]
    internal class HighlightWordTaggerProvider : IViewTaggerProvider
    {
        #region Imports

        [Import]
        internal ITextSearchService TextSearchService { get; set; }
        [Import]
        internal ITextStructureNavigatorSelectorService TextStructureNavigatorSelector { get; set; }

        [Import]
        internal Settings Settings { get; set; }

        #endregion Imports

        #region IViewTaggerProvider

        public ITagger<T> CreateTagger<T>( ITextView textView, ITextBuffer buffer ) where T : ITag
        {
            var tagger = buffer.Properties.GetOrCreateSingletonProperty( () => new SelectionHighlighter( textView, TextStructureNavigatorSelector, TextSearchService, Settings ) );
            return (ITagger<T>)tagger;
        }

        #endregion IViewTaggerProvider
    }

    #endregion Provider
}
